/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.plan.colormark;

import org.mmtk.plan.TraceLocal;
import org.mmtk.policy.Space;
import org.mmtk.utility.Log;
import org.mmtk.utility.options.Options;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.ObjectReference;

/**
 * This class implements the thread-local functionality for a transitive closure
 * over a mark-sweep space.
 */
@Uninterruptible
public final class CMHelperTraceLocal extends CMTraceLocal {
	/****************************************************************************
	 * Instance fields
	 */
	private ObjectReference color;
	private CMHelper helper;
	private long seed;

	/**
	 * Constructor
	 */
	public CMHelperTraceLocal(CMHelper helper) {
		super(CM.SCAN_HELP, helper.getTrace());
		this.helper = helper;
		this.color = helper.getColor();
		this.seed = color.toAddress().toLong()>> 5 ;
	}

	/****************************************************************************
	 * Externally visible Object processing and tracing
	 */

	/**
	 * Is the specified object live?
	 * 
	 * @param object
	 *            The object.
	 * @return <code>true</code> if the object is live.
	 */
	@Override
	public boolean isLive(ObjectReference object) {
		if (object.isNull())
			return false;
		if (Space.isInSpace(CM.CM_SPACE, object)) {
			return CM.cmSpace.isLive(object);
		}
		return super.isLive(object);
	}

	/**
	 * This method is the core method during the trace of the object graph. The
	 * role of this method is to:
	 * 
	 * 1. Ensure the traced object is not collected. 2. If this is the first
	 * visit to the object enqueue it to be scanned. 3. Return the forwarded
	 * reference to the object.
	 * 
	 * In this instance, we refer objects in the mark-sweep space to the msSpace
	 * for tracing, and defer to the superclass for all others.
	 * 
	 * @param object
	 *            The object to be traced.
	 * @return The new reference to the same object instance.
	 */
	@Inline
	@Override
	public ObjectReference traceObject(ObjectReference object) {
		if (object.isNull()) {
			return object;
		}

		if (!Space.isInSpace(CM.CM_SPACE, object)) {
			return super.traceObject(object);
		}
		
		if (!VM.objectModel.validRef(object)) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\t !!! helper traceObject: invalid object=");
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			CM.releaseLogLock();
			return object;
		}
			
		/*
		if(VM.objectModel.isArray(object)) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\t !!! helper traceObject: array object=");
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			CM.releaseLogLock();
			return object;
		}
			
		if(VM.objectModel.isPrimitiveArray(object)) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\t !!! helper traceObject: array object=");
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			CM.releaseLogLock();
			return object;
		}
		*/

		if (CM.cmSpace.isLive(object)) {
			// traced by regular tracer - do nothing
			return object;
		}

		ObjectReference objColor = VM.objectModel.getKathy(object);
		if (objColor == color) {
			// traced by this helper? - do nothing
			return object;
		}

		if (objColor != ObjectReference.nullReference()) {
			// traced by other helper - mark dependent helper
			// TODO do this atomically to prevent unneeded dependencies among
			// colors
			CMHelper otherHelper = CM.getHelper(objColor);
			if (otherHelper == null) {
				if (Options.verbose.getValue() >= 3) {
					CM.acquireLogLock();
					Log.writeln("\t !!! traceObject, other helper is null");
					Log.flush();
					VM.objectModel.dumpObject(object);
					CM.releaseLogLock();
				}
			} else {
				helper.addColor(objColor);
			}
			return object;
		}

		// TODO ! atomically set the color
		VM.objectModel.setKathy(object, color);
		if (Options.verbose.getValue() >= 3) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\thelper setting color: color=");
			Log.write(color);
			Log.write(", object=");
			Log.writeln(object);
			Log.flush();
			// VM.objectModel.dumpObject(object);
			CM.releaseLogLock();
		}
		helper.addTraced();
		processNode(object);
		return object;
	}

	protected void giveUpRoots() {
		this.rootLocations.flushLocal();
	}

	public void helperTrace() {
		//TODO repeat this till the trace is over
		obtainRoot();
		completeTrace();
		if (Options.verbose.getValue() >= 1) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\thelperTrace, getTraced=");
			Log.writeln(helper.getTraced());
			Log.flush();
			CM.releaseLogLock();
		}
	}
	
	private void obtainRoot() {
		long newSeed;
		ObjectReference object;

		newSeed = this.seed++ ^ helper.getRoots();
		object = CM.cmSpace.getAnyValidObject(newSeed);

		if (object == ObjectReference.nullReference()) {
			CM.acquireLogLock();
			Log.writeln("\t !!! prepare, object is NULL");
			Log.flush();
			CM.releaseLogLock();
			return;
		}

		if (CM.cmSpace.isLive(object)) {
			CM.acquireLogLock();
			Log.writeln("\t !!! prepare, object is marked");
			Log.flush();
			CM.releaseLogLock();
			return;
		}

		if (VM.objectModel.isArray(object)) {
			CM.acquireLogLock();
			Log.writeln("\t !!! prepare, object is array");
			Log.flush();
			CM.releaseLogLock();
			return;
		}

		if (VM.objectModel.isPrimitiveArray(object)) {
			CM.acquireLogLock();
			Log.writeln("\t !!! prepare, object is primitive array");
			Log.flush();
			CM.releaseLogLock();
			return;
		}

		if (Options.verbose.getValue() >= 3) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\tobtainRoot, object=");
			Log.flush();
			VM.objectModel.dumpObject(object);
			Log.flush();
			CM.releaseLogLock();
		}
		VM.objectModel.setKathy(object, color);
		helper.addRoot();
		this.processNode(object);
	} 


	public void prepare() {
	}
}
